/**
 * @mainpage RoundSnake
 *
 * Gioco basato sul classico "snake", con una veste grafica rinnovata e l'inserimento di alcune varianti.
 * Un serpente, rappresentato come una catena di sfere si muove all'interno di un labirinto cercando di
 * raccogliere quanto più cibo possibile. Ogni qualvolta viene raccolto del cibo, aumenta la lunghezza
 * del serpente e viene incrementato il punteggio. La velocità del gioco aumenta all'aumentare della lunghezza.
 * Il gioco termina quando la testa di snake colpisce il corpo stesso o le pareti del labirinto.
 * E' implementata inoltre la gestione dei record, che permette di inserire,
 * salvare e caricare i dieci migliori punteggi effettuati in partita.
 *
 * Vedere la documentazione della funzione ::main per maggiori dettagli
 * sulle funzionalità e sulla loro implementazione.
 *
 * @author Pietro Ceddia
 */

/**
 *@file
 * File contenente la funzione ::main e le funzioni per allocazione/deallocazione memoria e caricamento librerie  
 */

#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
#include <iostream>
#include <fstream>
#include <allegro5/allegro_ttf.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_audio.h>
#include <allegro5/allegro_acodec.h>
#include <allegro5/allegro_native_dialog.h>

using namespace std;

#include "struttura_dati.h"
#include "sn_main.h"
#include "menu.h"
#include "gioco.h"
#include "gestione_record.h"

#ifdef DEBUG_MODE
unsigned int MASK = 1;
#endif




#define screen_w 800 /**<larghezza finestra*/
#define screen_h 600 /**<altezza finestra*/

static float buf_posx,/**<coordinata x per centrare l'immagine sul display*/
	   buf_posy,/**<coordinata y per centrare l'immagine sul display*/
	   scala;/**<indica se e di quanto scalare l'immagine*/

 
ALLEGRO_DISPLAY *display=NULL;
	
/**
 * finestra usata come buffer per disegnare le immagini di
 * gioco prima di essere scalate e rappresentate sul ::display
 */
ALLEGRO_BITMAP *buffer=NULL;
	
ALLEGRO_SAMPLE *song=NULL;/**<traccia audio riprodotta in background*/
ALLEGRO_SAMPLE *game_over=NULL; /**<traccia riprodotta a fine partita*/
ALLEGRO_SAMPLE *preso_cibo=NULL,*selezione=NULL;

ALLEGRO_EVENT_QUEUE *event_queue=NULL;/**<Oggetto in cui vengono immagazzinati gli eventi ricevuti*/
ALLEGRO_TIMER *timer=NULL;

ALLEGRO_BITMAP * logo=NULL,* pareti=NULL, *head=NULL,* img_cibo=NULL,* body=NULL; 
ALLEGRO_FONT *ysel=NULL, *nsel=NULL;

 
/**
 * Inizializza librerire allegro.
 * Funzione privata che inizializza la libreria grafica allegro, abilita i driver tastiera  
 * gli addon per la gestione delle immagini,il caricamento e la stampa dei caratteri e  
 * e la riproduzione di campioni audio, da diversi tipi di file. 	 
 */	
void inizializza_allegro(){

	if(!al_init()){
		al_show_native_message_box(display,"Errore", "Errore riscontrato:",
		"Impossibile inizializzare libreria allegro",NULL,ALLEGRO_MESSAGEBOX_ERROR);
		exit(1);}
	if (!al_install_keyboard()){
		al_show_native_message_box(display,"Errore", "Errore riscontrato:",
		"Impossibile inizializzare driver tastiera",NULL,ALLEGRO_MESSAGEBOX_ERROR);
		exit(1);}
	if(!al_init_image_addon()){
		al_show_native_message_box(display,"Errore", "Errore riscontrato:",
		"Impossibile inizializzare libreria immagini",NULL,ALLEGRO_MESSAGEBOX_ERROR);	
		exit(1);}
	al_init_font_addon();
	if(!al_init_ttf_addon()){
		al_show_native_message_box(display,"Errore", "Errore riscontrato:",
		"Impossibile inizializzare font ttf",NULL,ALLEGRO_MESSAGEBOX_ERROR);
		exit(1);}
	if(!al_install_audio()){
		al_show_native_message_box(display,"Errore", "Errore riscontrato:",
		"Impossibile inizializzare libreria gestione audio",NULL,ALLEGRO_MESSAGEBOX_ERROR);	
		exit(1);}
	if(!al_init_acodec_addon()){
		al_show_native_message_box(display,"Errore", "Errore riscontrato:",
		"Impossibile inizializzare codec audio aggiuntivi",NULL,ALLEGRO_MESSAGEBOX_ERROR);		
		exit(1);}
	if(!al_reserve_samples(4)){
		al_show_native_message_box(display,"Errore", "Errore riscontrato:",
		"Impossibile inizializzare istanze audio",NULL,ALLEGRO_MESSAGEBOX_ERROR);
		exit(1);}
}

/**
 * Inizializza il ::display con le dimensioni dello schermo per permettere
 * la visualizzazione in fullscreen ed il ::buffer con dimensioni 800*600
 * esegue dei calcoli per poter scalare il buffer sul display in base alla
 * corrente risoluzione del monitor
 */
void inizializza_display_buffer(){

#ifndef DEBUG_MODE /*<se non sono in modalita debug imposto la modalità fullscreen altrimenti in modalità finestra*/	
	ALLEGRO_DISPLAY_MODE info_disp;/*<tipo strutturato che fornisce diverse info circa la risoluzione*/ 
	
	al_get_display_mode(al_get_num_display_modes()-1, &info_disp);/*<fornisce la risoluzione attuale*/ 
	
	al_set_new_display_flags(ALLEGRO_FULLSCREEN_WINDOW);/*<imposta modalità schermo intero */

	/* creo un display con le dimensione della risoluzione corrente in uso
 	 * e con i parametri definiti da al_set_new_display_flag
 	 */ 
	display = al_create_display(info_disp.width,info_disp.height);
#else
	display=al_create_display(screen_w,screen_h);
#endif
	if(!display){
		al_show_native_message_box(display,"Errore", "Errore riscontrato:",
		"Impossibile creare display",NULL,ALLEGRO_MESSAGEBOX_ERROR);
		exit(1);}
	/*creo una bitmap con le dimensioni della finestra di gioco */
	buffer = al_create_bitmap(screen_w, screen_h);
	if(!buffer){
		al_show_native_message_box(display,"Errore", "Errore riscontrato:",
		"Impossibile creare bitmap buffer",NULL,ALLEGRO_MESSAGEBOX_ERROR);
		exit(1);}

	/*inizializzo due variabili con, rispettivamente la base e l'altezza
 	 *del display appena creato. Mi serviranno per calcolare quanto e se scalare
 	 *l'immagine(in caso di risoluzione più bassa del mio bitmap "buffer" e
 	 *per centrarla all'interno del display
 	 */
	int miodisp_w=al_get_display_width(display);
	int miodisp_h=al_get_display_height(display);
	
	/* inizializzo scala a 1 perché se il display ha dimensioni maggiori o uguali
 	 * a quelle del buffer centro il buffer senza scalarlo (cioè moltiplicando per 1)
 	 */
	scala=1; 

	/*verifico che la risoluzione dello schermo non sia più bassa della finestra. In tal caso scalo l'immagine */
	if(miodisp_w <screen_w || miodisp_h<screen_h){
		float sc_w=(float)miodisp_w/screen_w;
		float sc_h=(float)miodisp_h/screen_h;
		/*prendo il valore più piccolo per il quale moltiplicare*/	
		scala=sc_w<sc_h ? sc_w : sc_h;}   

	/*calcolo coordinate x e y per centrare l'immagine*/
	buf_posx=(miodisp_w-screen_w*scala)/2.;
	buf_posy=(miodisp_h-screen_h*scala)/2.;
}



void dealloca_oggetti(){
	
	al_destroy_display(display);/*dealloca display*/
	al_destroy_bitmap(buffer);/*dealloca buffer*/
	al_destroy_event_queue(event_queue);/*dealloca coda eventi*/
	al_destroy_timer(timer);/*dealloca timer*/
	al_destroy_bitmap(logo);/*dealloca bitmap logo*/
	al_destroy_bitmap(pareti);/*dealloca bitmap pareti*/
	al_destroy_bitmap(head);/*dealloca bitmap che rappresenta la testa di snake*/
	al_destroy_bitmap(img_cibo);/*dealloca bitmap che rappresenta immagine del cibo*/
	al_destroy_bitmap(body);/*dealloca bitmap che rappresenta il corpo di snake*/
	al_destroy_font(ysel);/*dealloca carattere selezione attiva*/
	al_destroy_font(nsel);/*dealloca carattere selezione non attiva*/
	al_destroy_sample(song);/*dealloca traccia musicale background*/
	al_destroy_sample(preso_cibo);/*dealloca suono riprodotto quando viene preso il cibo*/
	al_destroy_sample(selezione);/*dealloca suono riprodotto quando si seleziona un opzione dal menu*/
	al_destroy_sample(game_over);/*dealloca suono riprodotto a fine partita*/
	D1(cout<<"oggetti deallocati"<<endl);
}
	
/**
 * Crea coda eventi e timer e carica oggetti da file.
 * Funzione privata che effettua i caricamenti da file degli oggetti che saranno utilizzati durante
 * il gioco quali immagini, file ttf , e file audio. In caso di mancato caricamento di uno dei file
 * dealloca la memoria precedentemente occupata dagli altri oggetti tramite ::dealloca_oggetti
 * e termina l'esecuzione del programma
 */	
void crea_carica_oggetti(){

	event_queue = al_create_event_queue();/*inizializza coda eventi*/
   	if(!event_queue) {
	  	al_show_native_message_box(display,"Errore", "Errore riscontrato:",
	  	"Impossibile inizializzare coda eventi",NULL,ALLEGRO_MESSAGEBOX_ERROR);
      	al_destroy_display(display);
      	al_destroy_bitmap(buffer);
	  	exit(1);}

	timer=al_create_timer(t_zero);/*Inizializza timer con velocità iniziale t_zero*/ 
	if(!timer) {
		al_show_native_message_box(display,"Errore", "Errore riscontrato:",
	  	"Impossibile inizializzare timer",NULL,ALLEGRO_MESSAGEBOX_ERROR);
		al_destroy_display(display);
	  	al_destroy_bitmap(buffer);
      	exit(1);}		
	
	ysel=al_load_ttf_font("./img/STONB.TTF",60,0);/*carica font ttf in dimensione 60*/ 
	nsel=al_load_ttf_font("./img/STONB.TTF",30,0);/*carica font ttf in dimensione 30*/ 
	logo=al_load_bitmap("./img/Sn_light.png");
	pareti=al_load_bitmap("./img/cornice.png");
	head=al_load_bitmap("./img/testa.png");
	body=al_load_bitmap("./img/body.png");
	img_cibo=al_load_bitmap("./img/topo.png");
	song = al_load_sample( "./audio/funny_move.wav" );
	preso_cibo=al_load_sample("./audio/preso_cibo.wav");
	selezione=al_load_sample("./audio/selezione.wav");
	game_over=al_load_sample("./audio/fail.wav");
	/* Verifico che non ci siano stati errori nel carimento delle immagini
	 * In tal caso il programma restituisce un messaggio di errore e distrugge
	 * l'immagine di buffer e il display
	 */
	if(!ysel || !nsel || !logo || !pareti || !head || !body 
		|| !img_cibo || !song || !preso_cibo|| !selezione ||!game_over){
		al_show_native_message_box(display,"Errore", "Errore riscontrato:",
	  	"Impossibile caricare file audio e/o immagini e/o font\n",NULL,ALLEGRO_MESSAGEBOX_ERROR);
		dealloca_oggetti();	
		exit(1);}   
}




/**  
* Funzione principale con le funzionalità di base.
* Il main invoca le funzioni: 
* - ::inizializza_allegro per abilitare le funzionalità di allegro.
* - ::inizializza_display_buffer per creare la finestra di gioco.  
* - ::crea_carica_oggetti per effettuare i caricamenti da file. 
* @n Dopo aver settato alcune variabili interne fa partire il ciclo infinito
* ed invoca le vare funzioni per l'implementazione del menu della parte di
* gioco e della gestione dei record. @n In uscita dealloca tutti gli oggetti
* presenti in memoria tramite::dealloca_oggetti.
*/

int main(int argc, char *argv[]){   

inizializza_allegro();
inizializza_display_buffer();
crea_carica_oggetti();
	
const int num_opzioni=3;
testo t_sel[num_opzioni];

t_sel[0].carattere=ysel;t_sel[1].carattere=nsel; t_sel[2].carattere=nsel; 
t_sel[0].px=t_sel[1].px=t_sel[2].px=550;
t_sel[0].py=150;t_sel[1].py=280;t_sel[2].py=410;
t_sel[0].text="New Game";t_sel[1].text="Record";t_sel[2].text="Exit";


int punteggio;
l_record lista;
scelta scelta_s=menu;
caratteristiche snake[max_sn], cibo;
direzione direzione_s;

al_register_event_source(event_queue, al_get_keyboard_event_source());
al_register_event_source(event_queue, al_get_timer_event_source(timer));

srand(al_get_time());/*cambia seme per il generatore di numeri casuali*/
al_start_timer(timer);/*fa partire il timer*/
bool disegna=true;/*Permette di ridisegnare le immagini sul buffer e sul display solo se settata a true*/
bool primo_menu=true;/*prima esecuzione case menu*/
bool primo_gioco=true;/*prima esecuzione case gioco*/
bool primo_record=true;/*prima esecuzione case record*/
bool primo_prendi_punti=true;/*prima esecuzione case prendi punti*/

al_play_sample(song, 0.7, 0.0,1.0,ALLEGRO_PLAYMODE_LOOP, NULL);/*avvia riproduzione traccia musicale*/

while(true){
ALLEGRO_USTR *input;/*stringa di testo fornita da allegro*/
ALLEGRO_EVENT ev;/*contiene info riguardo l'evento ricevuto*/

if(disegna){
al_set_target_bitmap(buffer);
al_clear_to_color(al_map_rgb(0,0,0));
D2(cout<<"ho ripulito il buffer\n");
}

switch (scelta_s){

	case menu:
			if(!primo_menu){
				do
					al_wait_for_event(event_queue, &ev);
				while (ev.type != ALLEGRO_EVENT_KEY_DOWN);
					cambia_selezione(ev,t_sel,num_opzioni,scelta_s);
				}
			disegna_menu(t_sel,num_opzioni);
			primo_menu=false;
	break;
	

	case gioco:{
		bool th;
		disegna=false;		
				
		if(primo_gioco){
			resetta_snake(snake,punteggio,direzione_s,cibo);			
			primo_gioco=false;
			primo_menu=true;
			}
		/* mi metto in attesa di eventi quali il timer o input da tastiera*/
		al_wait_for_event(event_queue, &ev);
		/*se l'evento è di tipo pressione tasto e l'ultimo event era del timer cambio la direzione */		
		if(ev.type == ALLEGRO_EVENT_KEY_DOWN && th ){ 
			cambia_direzione(event_queue,ev,direzione_s);//alla funzione mando l'evento e la direzione attuale(per riferimento)
			th=false;}
		
		if(ev.type==ALLEGRO_EVENT_TIMER){
			/*-ogni volta che uso il timer setto th a true, e quando arriva event tastiera verifico che sia vera
			 * per evitare che premendo velocemente due volte i tasti freccia il serpente si rigiri su se stesso
		 	 *-Setto anche disegna a true per disegnare la nuova immagine sul display e ripulire il backbuffer.
			 */				
			th=disegna=true;
							
			
			/*cambio posizione al serpente. Se la funzione ritorna false ho perso.
			 *Quindi assegno scelta_s=menu per ristampare il menu e primo_gioco=true
			 *nel caso volessi iniziare una nuova partita.
			 */
			if(!cambia_posizione(snake,direzione_s, punteggio,cibo)){
				D1(cout<<"punteggio: "<<punteggio<<endl;				
					cout<<"coordinate x e y della testa: "<<snake[0].pos_x<<"  "<<snake[0].pos_y<<endl;
					cout<<"coordinate x e y del cibo: "<<cibo.pos_x<<" "<<cibo.pos_y<<endl;				
					cout<<"coordinate x e y del corpo: \n";
					for(int i=1;i<=punteggio;i++){
						cout<<"snake["<<i<<"].pos_x: "<<snake[i].pos_x<<"\t";
						cout<<"snake["<<i<<"].pos_y: "<<snake[i].pos_y<<endl;})
				al_play_sample(game_over, 1.2, 0.0,2.0,ALLEGRO_PLAYMODE_ONCE,NULL);
				scelta_s=prendi_punti;
				primo_gioco=true;
				break;}
		/*disegno l'interfaccia di gioco. */								
		disegna_gioco(cibo,snake,direzione_s,punteggio);}
	break;
		}
	
	case record:
		if(primo_record){
			carica_lista(lista, true);
			primo_record=false;}		
		else{
			al_rest(5.00);
			al_flush_event_queue(event_queue);
			scelta_s=menu;
			primo_record=true; 	
			primo_menu=true;}
		break;
	
	case prendi_punti:{
		/* Se è il primo giro del case "prendi punti", alloco  la memoria
		 * per una nuova stringa e la inizializzo come vuota, assegno primo_prendi_punti=false
		 * così da non enetrare nell'if al giro successivo */		
		if(primo_prendi_punti){
			input=al_ustr_new("");
			primo_prendi_punti=false;}
		else{			
			do
				al_wait_for_event(event_queue, &ev);
			while(ev.type!=ALLEGRO_EVENT_KEY_CHAR);			
			int codice_car=ev.keyboard.unichar;
			if 		(codice_car == 13 && al_ustr_length(input)>0){
					carica_lista(lista,false);
					inserisci_in_ordine(lista,punteggio,al_cstr(input));
					al_ustr_free(input);
					scelta_s=menu;
					primo_prendi_punti=true;} 
			else if((codice_car ==127||codice_car==8) && al_ustr_length(input)>0)
					al_ustr_remove_chr(input, al_ustr_offset(input, -1));
			else if (((codice_car >= 48 && codice_car<=57)||(codice_car >=65 && codice_car<=90)||
					(codice_car>=97 && codice_car<=122)) && al_ustr_length(input)<MAX_NOME)
					al_ustr_append_chr(input, codice_car);
			}	

		al_draw_text(ysel,al_map_rgb(255,127,50),400,200,ALLEGRO_ALIGN_CENTRE,"Nome Giocatore:");
		al_draw_ustr(ysel,al_map_rgb(255,127,50),400,300 ,ALLEGRO_ALIGN_CENTRE,input);              	
		break;}
	
	case uscita:
		al_rest(0.50);
		D1(cout<<"dealloco oggetti in memoria\n");
		dealloca_oggetti();
		return 0;
			 	
/*fine switch*/
}


		
			
if(disegna){
al_set_target_backbuffer(display);
al_clear_to_color(al_map_rgb(0,0,0));
al_draw_scaled_bitmap(buffer, 0,0, screen_w, screen_h, buf_posx, buf_posy, screen_w*scala, screen_h*scala, 0);
al_flip_display();
D2(cout<<"ho disegnato sul display\n";)
}
D2(cout<<"giro di while\n"); 

}

return 0;}



